package nat_detect

import (
	"fmt"
	"net"
	"time"
)

const (
	FullCone  = 1
	ResCone   = 2 // ip受限
	ResPort   = 3 //端口受限
	Symc      = 4 //对称
	UdpBlock1 = 5
	UdpBlock2 = 6
	UNKNOW    = 7
)

// 尝试多次发送，接收到数据就停止
func CommonSendAndRespone(cmd []byte, conn *net.UDPConn, remote *net.UDPAddr, sessionId uint64) string {
	ch := make(chan string)
	const retryTime = time.Millisecond * 300
	go func() {
		for {
			buf := make([]byte, 512)
			conn.SetReadDeadline(time.Now().Add(retryTime * 5))
			n, _, err := conn.ReadFromUDP(buf)
			if err != nil {
				fmt.Println("[read respone timeout]:", err)
				ch <- ""
				return
			}
			responePkt := ParsePkt(buf[:n])
			if responePkt == nil {
				fmt.Println("CommonSendAndRespone error : responePkt = nil")
				ch <- ""
				return
			}
			//fmt.Println("CommonSendAndRespone: ",responePkt.String())
			if responePkt.Id != sessionId {
				fmt.Println("skip packet")
				continue
			}
			ch <- responePkt.Remote
			break
		}
	}()

	timer1 := time.NewTimer(retryTime)
	conn.WriteToUDP(cmd, remote)
	fmt.Println("send to ", remote.String())

	for i := 0; i < 3; i++ {
		select {
		case remoteIpPort := <-ch:
			timer1.Stop()
			return remoteIpPort
		case <-timer1.C:
			//fmt.Println("try ", i+1)
			_, err := conn.WriteToUDP(cmd, remote)
			if err != nil {
				fmt.Println("WriteToUDP err", err)
			}
			timer1.Reset(time.Second)
		}
	}
	return ""
}

func DoTest1(conn *net.UDPConn, remote *net.UDPAddr) (bool, string) {
	fmt.Println("\n#### DoTest1 #####")
	cmd, sessionId := MakePktWithCmd("test1")
	if len(cmd) == 0 {
		fmt.Println("make pkt error")
		return false, ""
	}
	remoteIPPort := CommonSendAndRespone(cmd, conn, remote, sessionId)
	if len(remoteIPPort) == 0 {
		fmt.Println("remoteIPPort is empty")
		return false, ""
	}
	fmt.Println("DoTest1 ok , return   publicip ", remoteIPPort)
	return true, remoteIPPort
}

func DoTest2(conn *net.UDPConn, remote *net.UDPAddr) bool {
	fmt.Println("\n#### DoTest2 ####")
	cmd, sessionId := MakePktWithCmd("test2")
	remoteIPPort := CommonSendAndRespone(cmd, conn, remote, sessionId)
	if len(remoteIPPort) == 0 {
		fmt.Println("DoTest2 failed")
		return false
	}
	fmt.Println("DoTest2 get echo ", remote.String(), remoteIPPort)
	return true
}
func DoTest3(conn *net.UDPConn, remote *net.UDPAddr) bool {
	fmt.Println("\n#### DoTest3 ####")
	cmd, sessionId := MakePktWithCmd("test3")
	remoteIPPort := CommonSendAndRespone(cmd, conn, remote, sessionId)
	//fmt.Println("DoTest3 remote publicip ", remoteIPPort)
	if len(remoteIPPort) == 0 {
		fmt.Println("DoTest3 failed")
		return false
	}
	fmt.Println("DoTest3 get echo ", remote.String(), remoteIPPort)
	return true
}
func ParseIpport(ipport string) string {
	host, _, err := net.SplitHostPort(ipport)
	if err == nil {
		return host
	}
	return ""
}
func DoTest() (int, int) {
	conn, _ := net.ListenUDP("udp", nil)
	remoteServer1, _ := net.ResolveUDPAddr("udp", "101.37.33.124:9999")
	remoteServer2, _ := net.ResolveUDPAddr("udp", "39.105.141.60:9999")
	test1ok, remotePublicIp := DoTest1(conn, remoteServer1)
	if !test1ok {
		//udp block
		fmt.Println("result: test1 udp block")
		return UdpBlock1, 0
	} else {
		fmt.Println("test1ok", remotePublicIp)
	}

	test2ok := DoTest2(conn, remoteServer1)
	//test2ok = false
	if test2ok {
		fmt.Println("result: full cone")
		return FullCone, 0
	}
	anotherTest1ok, anotherRemotePublicIp := DoTest1(conn, remoteServer2)
	if !anotherTest1ok {
		//udp block
		fmt.Println("result: test1 udp block for another server")
		return UdpBlock2, 0
	}
	if remotePublicIp != anotherRemotePublicIp {
		remoteHost := ParseIpport(remotePublicIp)
		anotherHost := ParseIpport(anotherRemotePublicIp)
		if len(remoteHost) == 0 && len(anotherHost) == 0 {
			return UNKNOW, 0
		}
		if remoteHost == anotherHost { //ip相同，端口不同，判断为对称
			fmt.Println(remotePublicIp, " != ", anotherRemotePublicIp)
			fmt.Println("result: symmetric")
			return Symc, 0
		} else {
			//test3
			//测试从相同的机器，不同端口返回
			fmt.Println("multi-ip")
			test3ok := DoTest3(conn, remoteServer1)
			if test3ok {
				fmt.Println("result: restricted cone")
				return ResCone, 1
			} else {
				//todo 到这一步，还是判断不出是什么类型,可能是对称或ResPort 暂时认为是对称
				fmt.Println("result: symmetric")
				return Symc, 1

			}
		}
	}
	//test3
	//测试从相同的机器，不同端口返回
	test3ok := DoTest3(conn, remoteServer1)
	if test3ok {
		fmt.Println("result: restricted cone")
		return ResCone, 0
	} else {
		fmt.Println("result: restricted port")
		return ResPort, 0
	}
}
